/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.jfonia.model;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.jfonia.connect5.intervals.Interval;
import org.jfonia.connect5.intervals.ListObserver;
import org.jfonia.connect5.intervals.ObservableLinkedIntIntervalList;
import org.jfonia.connect5.relations.MergeRelation;
import org.jfonia.connect5.relations.MergeRelationInt;
import org.jfonia.model.elements.MusicalElement;

/**<i
 *
 * @author jannes
 */
public class Sequence<T extends MusicalElement> extends ObservableLinkedIntIntervalList implements ISequence,ListObserver{
    protected Class templateClass;

    public Sequence(Class<T> templateClass){
        this.templateClass = templateClass;
        this.addObserver(this);
    }

    public Class getTemplateClass(){
        return templateClass;
    }

    public void setLockPerformance(boolean lock){}

    public void setLockNotation(boolean lock){}

    public MusicalElement getMusicalElementFromIndex(int index){
        return (MusicalElement)getIntervalFromIndex(index);
    }

    //Auxiliary methods for common interface ISequence (used by CommandStack)
    public MusicalElement getMusicalElement(int tick) {
        return (MusicalElement)getIntervalFromValue(tick);
    }

    public MusicalElement addMusicalElement(MusicalElement musicalElement) {
        if(musicalElement.getClass() == templateClass){
            add(musicalElement);            
            return musicalElement;
        }else{
            System.err.println("wrong type of musical element(add)");
            return null;
        }
        
    }

    public MusicalElement insertMusicalElement(int beginTick, MusicalElement musicalElement) {
        if(musicalElement.getClass() == templateClass){
            if(beginTick > getDifference())
            {
                    add(musicalElement);                    
                    return musicalElement;
            }
            MusicalElement previousElement = (MusicalElement)getIntervalFromValue(beginTick);
            if(previousElement == null || previousElement.getEnd() == beginTick)
                    add(musicalElement);
            else
            {
                    insert(indexOf(previousElement), musicalElement);
            }            
            return musicalElement;
        }else{
            System.err.println("wrong type of musical element(insert)");
            return null;
        }
    }

    public void addMusicalElements(List<MusicalElement> musicalElements)
    {
        for(MusicalElement musicalElement : musicalElements){            
            addMusicalElement(musicalElement);
        }
    }

    public void insertMusicalElements(int beginTick, List<MusicalElement> musicalElements)
    {
            int tickPosition = beginTick;
            for(MusicalElement musicalElement : musicalElements)
            {            
                insertMusicalElement(tickPosition, musicalElement);
                tickPosition += musicalElement.getDifference();
            }
    }

    public void removeMusicalElement(MusicalElement musicalElement) {
        int index = indexOf(musicalElement);
        if(index >= 0){
            remove(index);
            nodeRemoved(this, musicalElement);
        }
            
    }

    public int getMusicalLength() {
        return getDifference();
    }

    public int indexOf(MusicalElement MusicalElement) {
        return super.indexOf(MusicalElement);
    }
    //End auxiliary methods

    //Methods for observers
    Set<ISequenceObserver> observers = new LinkedHashSet<ISequenceObserver>();
    public void addObserver(ISequenceObserver o)
    {
        if (!observers.contains(o))
                observers.add(o);
    }

    public boolean containsObserver(ISequenceObserver o)
    {
        return observers.contains(o);
    }

    public void removeObserver(ISequenceObserver o)
    {
        observers.remove(o);
    }

    public void nodeAdded(Sequence sequence, MusicalElement musicalElement){
        for(ISequenceObserver observer : observers){
            observer.nodeAdded(sequence,musicalElement);
        }
    }

    public void nodeRemoved(Sequence sequence, MusicalElement musicalElement){
        for(ISequenceObserver observer : observers){
            observer.nodeRemoved(sequence,musicalElement);
        }
    }

    public void nodeDifferenceChanged(Sequence sequence, MusicalElement musicalElement){
        for(ISequenceObserver observer : observers){
            observer.nodeDifferenceChanged(sequence, musicalElement);
        }
    }

    public void intervalAdded(Interval interval) {
        nodeAdded(this, (MusicalElement)interval);
    }

    public void intervalRemoved(Interval interval, int index) {
        nodeRemoved(this, (MusicalElement)interval);
    }

    public void intervalEndChanged(Interval interval) {
        nodeDifferenceChanged(this, (MusicalElement)interval);
    }
    //End observer methods

    public void shift(int shiftLength){
        shiftLength %= getDifference();
        int newEnd = getDifference()-shiftLength;
        MusicalElement musicalElement = (MusicalElement)getIntervalFromValue(newEnd);
        int startIndex = indexOf(musicalElement);
        MusicalElement musicalElementNew = null;
        if(musicalElement.getBegin().longValue() != newEnd){//splitting required;
            musicalElementNew = musicalElement.clone();
            musicalElementNew.getDifferenceNode().setValue(newEnd-musicalElement.getBegin());
            insertMusicalElement(startIndex, musicalElementNew);
            musicalElement.setDifference(musicalElement.getDifference()-musicalElementNew.getDifference());
            startIndex++;
        }
        int size = size();
        MergeRelation.enableMerge.setValue(false);
        for(int i = startIndex; i < size; i++){
            int index = size()-1;
            MusicalElement me = (MusicalElement)getIntervalFromIndex(index);
            removeMusicalElement(me);
            //intervalRemoved will remove the MusicalElement
            insertMusicalElement(0,me);
        }
        if(musicalElementNew != null){
            new MergeRelationInt(musicalElementNew, musicalElement, this);
        }
        MergeRelation.enableMerge.setValue(true);
    }

}
